#include <stdio.h>
#include <cuda.h>
#include "stateCuda.h"
#include "state.h"
#include "stack.h"
#include "main.h"
#include "util.h"

/*typedef struct{
	int index;
	Point point1, point2;
	float result;
}Progress;*/

int countChildrenPoints(Point **descPoints, State *state);
__global__
void generateChildren_Kernel(float *childrenStatesValues, Point *parentStatePoints, Point *descPoints);
__device__
int parentStatePointsDepth;

void generateChildren(State *state) {
	printf("\tGeneruji potomky\n");
	/* Pole bodů, na které se budou generovat potomci */
	Point *descPoints = NULL;
	/* Počet těchto bodů */
	int descPointsCount = countChildrenPoints(&descPoints, state);
	/* Hodnoty nových stavů */
	float *childrenStatesValues = (float *)malloc(descPointsCount * sizeof(float));
	if(childrenStatesValues == NULL){
		fprintf(stderr, "Out of memory during generating children.\n");
		exit(EXIT_FAILURE);
	}
	/* Zkopírujeme body původního stavu na zařízení */
	cudaError_t error;
	Point *d_parentStatePoints;
	error = cudaMalloc(&d_parentStatePoints, state->depth * sizeof(Point));
	if(error != cudaSuccess){
		fprintf(stderr, "Error allocating memory 1 on CUDA.\n");
		exit(EXIT_FAILURE);
	}
	error = cudaMemcpy(d_parentStatePoints, state->statePoints, state->depth * sizeof(Point), cudaMemcpyHostToDevice);
	if(error != cudaSuccess){
		fprintf(stderr, "Error copying state points on CUDA.\n");
		exit(EXIT_FAILURE);
	}
	/* Zkopírujeme tam i body, podle kterých se budou počítat hodnoty nových stavů */
	Point *d_descPoints;
	error = cudaMalloc(&d_descPoints, descPointsCount * sizeof(Point));
	if(error != cudaSuccess){
		fprintf(stderr, "Error allocating memory 2 on CUDA.\n");
		exit(EXIT_FAILURE);
	}
	error = cudaMemcpy(d_descPoints, descPoints, descPointsCount * sizeof(Point), cudaMemcpyHostToDevice);
	if(error != cudaSuccess){
		fprintf(stderr, "Error copying descendant points on CUDA.\n");
		exit(EXIT_FAILURE);
	}
	/* Naalokujeme paměť pro tyto hodnoty */
	float *d_childrenStatesValues;
	error = cudaMalloc(&d_childrenStatesValues, descPointsCount * sizeof(float));
	if(error != cudaSuccess){
		fprintf(stderr, "Error allocating memory 3 on CUDA.\n");
		exit(EXIT_FAILURE);
	}
	cudaMemcpyToSymbol("parentStatePointsDepth", &(state->depth), sizeof(int), 0, cudaMemcpyHostToDevice);
	/*Progress *progresses = (Progress *)malloc(sizeof(Progress) * descPointsCount);
	Progress *d_progresses;
	cudaMalloc(&d_progresses, sizeof(Progress) * descPointsCount);
	cudaMemcpy(d_progresses, progresses, sizeof(Progress) * descPointsCount, cudaMemcpyDeviceToHost);*/
	/* Spustíme výpočet */
	generateChildren_Kernel<<<1, descPointsCount>>>(d_childrenStatesValues, d_parentStatePoints, d_descPoints);
	/* A zkopírujeme si zpět výsledky */
	//cudaMemcpy(progresses, d_progresses, sizeof(Progress) * descPointsCount, cudaMemcpyDeviceToHost);
	error = cudaMemcpy(childrenStatesValues, d_childrenStatesValues, descPointsCount * sizeof(float), cudaMemcpyDeviceToHost);
	if(error != cudaSuccess){
		fprintf(stderr, "Error copying results from CUDA.\n");
		exit(EXIT_FAILURE);
	}

	error = cudaFree(d_parentStatePoints);
	if(error != cudaSuccess){
		fprintf(stderr, "Error freeing memory 1 on CUDA.\n");
		exit(EXIT_FAILURE);
	}
	error = cudaFree(d_descPoints);
	if(error != cudaSuccess){
		fprintf(stderr, "Error freeing memory 2 on CUDA.\n");
		exit(EXIT_FAILURE);
	}
	error = cudaFree(d_childrenStatesValues);
	if(error != cudaSuccess){
		fprintf(stderr, "Error freeing memory 3 on CUDA.\n");
		exit(EXIT_FAILURE);
	}
	/* Z daných výsledků vyrobíme nové stavy a pushneme je na stack */
	State *newState;
	int i;
	for(i = 0; i < descPointsCount; i++){
		newState = childState(state, descPoints[i].x, descPoints[i].y);
		newState->value = childrenStatesValues[i];
		stackPush(newState);
		printf("\tVyroben nový stav pro bod [%d,%d] s hodnotou %f\n", descPoints[i].x, descPoints[i].y, childrenStatesValues[i]);
	}

	//free(progresses);
	free(descPoints);
	free(childrenStatesValues);
}

int countChildrenPoints(Point **descPoints, State *state){
	int lastPointX = 0, lastPointY = 0;
	int i, j, l, n;
	int hasPoint;
	int descCount = 0;

	for(i = 0; i < state->depth; i++){
		if(lastPointX < state->statePoints[i].x){
			lastPointX = state->statePoints[i].x;
			lastPointY = state->statePoints[i].y;
		}
		else if(lastPointX == state->statePoints[i].x){
			lastPointY = maxValue(lastPointY, state->statePoints[i].y);
		}
	}
	descCount = m * (k - lastPointX - 1) + (m - lastPointY - 1);
	if(state->depth == 0){
		descCount++;
	}

	*descPoints = (Point *)malloc(sizeof(Point) * descCount);
	if(*descPoints == NULL){
		fprintf(stderr, "Out of memory during counting children points.\n");
		exit(EXIT_FAILURE);
	}
	n = 0;
	for(i = lastPointX; i < m; i++){
		for(j = 0; j < k; j++){
			if(j == 0 && i == lastPointX){
				j = lastPointY;
			}
			hasPoint = 0;
			/* prohledáváme fixní body */
			for(l = 0; l < f; l++){
				if(fixedPoints[l].x == i && fixedPoints[l].y == j){
					hasPoint = 1;
					break;
				}
			}
			if(hasPoint){
				continue;
			}
			/* prohledáváme již umístěné body */
			for(l = 0; l < state->depth; l++){
				if(state->statePoints[l].x == i && state->statePoints[l].y == j){
					hasPoint = 1;
					break;
				}
			}
			if(hasPoint){
				continue;
			}
			((*descPoints)[n]).x = i;
			((*descPoints)[n]).y = j;
			n++;
		}
	}
	return n;
}

__global__
void generateChildren_Kernel(float *childrenStatesValues, Point *parentStatePoints, Point *descPoints){
	int i = threadIdx.x;
	Point point1 = descPoints[i];
	Point point2;
	float res = 0;
	int j;
	for(j = 0; j < parentStatePointsDepth; j++){
		point2 = parentStatePoints[j];
		int resX = point1.x - point2.x;
		int resY = point1.y - point2.y;

		res += sqrt((float)((resX*resX) + (resY*resY)));
	}
	childrenStatesValues[i] = res;
}

